\n \u003C/property>\n \u003Cproperty name=\"useCodeAsDefaultMessage\" value=\"true\" />\n\u003C/bean>\n",[270,3493,3494,3499,3504,3509,3514,3519],{"__ignoreMap":11},[273,3495,3496],{"class":275,"line":276},[273,3497,3498],{},"\u003Cbean id=\"messageSource\">\n",[273,3500,3501],{"class":275,"line":12},[273,3502,3503],{}," \u003Cproperty name=\"sources\">\n",[273,3505,3506],{"class":275,"line":287},[273,3507,3508],{}," \u003Cplugin:list class=\"org.synyx.minos.message.ModuleMessageSource\"/>\n",[273,3510,3511],{"class":275,"line":293},[273,3512,3513],{}," \u003C/property>\n",[273,3515,3516],{"class":275,"line":299},[273,3517,3518],{}," \u003Cproperty name=\"useCodeAsDefaultMessage\" value=\"true\" />\n",[273,3520,3521],{"class":275,"line":305},[273,3522,3523],{},"\u003C/bean>\n",[41,3525,3526,3527,3530,3531,3534,3535,3537,3538,3540],{},"So this registers our",[270,3528,3529],{},"DispatchingMessageSource"," that gets injected into all beans within the context, implementing\n",[270,3532,3533],{},"ModuleMessageSource"," by Hera. This pretty much does the trick. The reason that we use",[270,3536,3533],{}," instead of\nSprings built-in",[270,3539,2855],{},"-interface is on the one hand so that we can do some performance-tweaks and on the\nother hand so that we dont get any “unwanted” implementations, which get to the context somehow.",[41,3542,3543,3544,3546],{},"With some simple dispatching logic within",[270,3545,3529],{}," we found a powerful way to conquer the insufficiency\nof Spring, in conjunction with our modular system.",[264,3548,3550],{"className":363,"code":3549,"language":365,"meta":11,"style":11},"List candidates = sources.getPluginsFor(getPrefixFromCode(code));\nfor (MessageSourcePlugin source : candidates) {\n MessageFormat format = resolveMessageWithSource(source, code, locale);\n if (null != format) {\n return format;\n }\n}\n",[270,3551,3552,3557,3562,3567,3572,3577,3582],{"__ignoreMap":11},[273,3553,3554],{"class":275,"line":276},[273,3555,3556],{},"List candidates = sources.getPluginsFor(getPrefixFromCode(code));\n",[273,3558,3559],{"class":275,"line":12},[273,3560,3561],{},"for (MessageSourcePlugin source : candidates) {\n",[273,3563,3564],{"class":275,"line":287},[273,3565,3566],{}," MessageFormat format = resolveMessageWithSource(source, code, locale);\n",[273,3568,3569],{"class":275,"line":293},[273,3570,3571],{}," if (null != format) {\n",[273,3573,3574],{"class":275,"line":299},[273,3575,3576],{}," return format;\n",[273,3578,3579],{"class":275,"line":305},[273,3580,3581],{}," }\n",[273,3583,3584],{"class":275,"line":311},[273,3585,320],{},[41,3587,3588,3589,2926,3592,3595],{},"By the way, we use this mechanism a lot when it comes to easily extending functionality of the framework-core including\n",[270,3590,3591],{},"HandlerInterceptor",[270,3593,3594],{},"PropertyEditorRegistrar"," and our Modules itself.",[658,3597,660],{},{"title":11,"searchDepth":12,"depth":12,"links":3599},[],[223],"2010-04-23T11:45:49","Many of the Web-Applications we develop for our customers are based upon our small Framework on top\\nof Spring / Spring MVC. This framework basically\\nbrings often used components ready-to-use (or ready to customize) and – of course – makes things even simpler than\\nSpring already does.",{},"/blog/modular-web-applications-based-on-spring",{"title":3397,"description":3606},"Many of the Web-Applications we develop for our customers are based upon our small Framework on top\nof Spring / Spring MVC. This framework basically\nbrings often used components ready-to-use (or ready to customize) and – of course – makes things even simpler than\nSpring already does.","blog/modular-web-applications-based-on-spring",[3609,3610,2900,3611,3612,680],"architecture","framework","modular","plugin","Many of the Web-Applications we develop for our customers are based upon our small Framework on top of Spring / Spring MVC. This framework basically brings often used components ready-to-use…","9CCujFfUkmvCk9K2-lTvameRh4QQAhnYiCvpeGTJeT8",{"id":3616,"title":3617,"author":3618,"body":3619,"category":3843,"date":3844,"description":3845,"extension":16,"link":3846,"meta":3847,"navigation":23,"path":3848,"seo":3849,"slug":3623,"stem":3850,"tags":3851,"teaser":3853,"__hash__":3854},"blog/blog/know-your-apis-lessons-learned-from-resourcebundle.md","Know your APIs – Lessons learned from ResourceBundle",[26],{"type":8,"value":3620,"toc":3841},[3621,3624,3627,3634,3637,3657,3674,3700,3706,3719,3732,3748,3757,3784,3787,3820,3823],[37,3622,3617],{"id":3623},"know-your-apis-lessons-learned-from-resourcebundle",[41,3625,3626],{},"Last week I spent some time hunting down an internationalization-issue that came along while developing for a recent\nproject. Let me explain what happened:",[41,3628,3629,3630,3633],{},"Message-Lookup – of course – always stands together with Locales (",[270,3631,3632],{},"java.util.Locale",") of the client the message is\nresolved for. The problem was, that messages for the English users were not resolved to the English translation, but to\nthe German one.",[41,3635,3636],{},"Within the project I am working on, there were the following message-files at that time:",[101,3638,3639,3645,3651],{},[104,3640,3641,3644],{},[270,3642,3643],{},"messages.properties"," (containing the english translation)",[104,3646,3647,3650],{},[270,3648,3649],{},"messages_de.properties"," (containing the German translation)",[104,3652,3653,3656],{},[270,3654,3655],{},"messages_it.properties"," (containing Italian translation)",[41,3658,3659,3660,3663,3664,3667,3668,3670,3671,3673],{},"Usually you provide a property-file per language containing all the translations. ",[270,3661,3662],{},"ResourceBundle"," uses a\nfallback-mechanism from the full locale down to more general ones (e.g. it first checks for ",[270,3665,3666],{},"messages_de_DE.properties","\nto ",[270,3669,3649],{}," down to ",[270,3672,3643],{}," in the end, being the overall default).",[41,3675,3676,3677,3679,3680,3683,3684,2995,3687,3690,3691,3693,3694,3696,3697,3699],{},"This actually makes much sense because in this way you can provide values for stuff relevant for all languages in\n",[270,3678,3643],{},", English language specific values in ",[270,3681,3682],{},"messages_en.properties"," and stuff that is different for\ndifferent english speaking Countries in files like ",[270,3685,3686],{},"messages_en_US.properties",[270,3688,3689],{},"messages_en_UK.properties"," and so\non. The fallback works perfect, because if you for example don’t specify US/UK specific files message-lookup for both\nen_US and en_UK result in resolving keys from ",[270,3692,3682],{},". Additionally, if ",[270,3695,3686],{},"\nwould exist but the key-lookup fails (the file does not provide a translation for the key), the key gets also looked up\nfrom ",[270,3698,3682],{}," (which may also provide the key).",[41,3701,3702,3703,3705],{},"In my concrete case, resolving of German values (coming from ",[270,3704,3649],{},") worked well, but if you change\nthe users locale to English, the keys also resolved to German values.",[41,3707,3708,3709,3711,3712,3715,3716,3718],{},"I did not create a ",[270,3710,3682],{}," because English should also be the overall fallback if no i18n for the\nrequested language was available. I thought if the user had the locale fr the system would check for\n",[270,3713,3714],{},"messages_fr.properties"," and then in ",[270,3717,3643],{},", which would display English messages for the user because no\nFrench translation is available. I debugged a while, basically within our own framework and later down to Springs i18n\nrelated classes and I could not find the mistake.",[41,3720,3721,3722,3725,3726,3731],{},"Then, when I excluded all possible mistakes on our side and inside the Spring Framework where our application is based\non, my way lead me down to ",[270,3723,3724],{},"java.util.ResourceBundle",". Since this class makes extensive use of caching (for good reasons\nof course) and static factory methods (which are almost impossible to debug) my way lead me to the API-Doc\nof ",[45,3727,3662],{"href":3728,"rel":3729,"title":3730},"http://api.synyx.de/j2sdk6/api/java/util/ResourceBundle.html",[49],"API-Doc of java.util.ResourceBundle",".\nHere I found the mistake that I made:",[3733,3734,3735],"blockquote",{},[41,3736,3737,3740,3741,3744,3745,96],{},[270,3738,3739],{},"getBundle"," uses the base name, the specified locale, and the default locale (obtained from ",[270,3742,3743],{},"Locale.getDefault",") to\ngenerate a sequence of ",[120,3746,3747],{},"candidate bundle names",[41,3749,3750,3751,3753,3754,3756],{},"I forgot that ",[270,3752,3662],{}," also looks up files for the JVMs Default-Locale before falling back to the base-file (\n",[270,3755,3643],{},"). The Default-Locale of my JVM is de_DE, which lead to the following path:",[101,3758,3759,3764,3768,3773,3778],{},[104,3760,3761,3763],{},[270,3762,3686],{}," (not found)",[104,3765,3766,3763],{},[270,3767,3682],{},[104,3769,3770,3772],{},[270,3771,3666],{}," (from Default-Locale, not found)",[104,3774,3775,3777],{},[270,3776,3649],{}," (from Default-Locale, FOUND)",[104,3779,3780,3781,3783],{},"(",[270,3782,3643],{}," was not checked because the key was found)",[41,3785,3786],{},"So the fix was easy. There are even several ways to fix it:",[101,3788,3789,3798,3807,3814],{},[104,3790,3791,3792,3794,3795,3797],{},"rename ",[270,3793,3643],{}," to ",[270,3796,3682],{}," (which leads to the French dude having to learn German).",[104,3799,3800,3801,3794,3803,3806],{},"copy ",[270,3802,3643],{},[270,3804,3805],{},"message_en.properties"," (this is copy paste but this could be solved within the\nbuild-process using mvn)",[104,3808,3809,3810,3813],{},"set the default-locale of the JVM to an English one by calling ",[270,3811,3812],{},"Locale.setDefault(englishLocale)"," early in\napplication-boot",[104,3815,3816,3817],{},"set the default-locale as commandline-argument of your JVM (e.g.",[270,3818,3819],{},"-Duser.language=en -Duser.country=US",[41,3821,3822],{},"And, last but not least:What do we (or better I)learn from this?",[101,3824,3825,3828,3831],{},[104,3826,3827],{},"My usual approach “first think about whats happening, if you cannot figure out what leads to the problem immediately\nattach debugger” was obviously not the best approach in this case (although starting immediately to debug has turned\nout to be a very efficient way to hunt down bugs for me in general).",[104,3829,3830],{},"Code is not the only place where bugs can be found. A big problem is also understanding the basic APIs you use.",[104,3832,3833,3834,3837,3840],{},"Even if you use high-level APIs (i18n in this case with about 5 delegates before the ResourceBundle was actually\nreached) you still need to know the very basics.",[3835,3836],"br",{},[638,3838,3839],{},"Reading APIs"," earlier (ok, the whole hunting only took about 1-2 hours, but still)or even (in a perfect world)\nknow all your APIs you use directly or indirectly would have saved some time.",{"title":11,"searchDepth":12,"depth":12,"links":3842},[],[223],"2010-04-21T11:36:20","Last week I spent some time hunting down an internationalization-issue that came along while developing for a recent\\nproject. Let me explain what happened:","https://synyx.de/blog/know-your-apis-lessons-learned-from-resourcebundle/",{},"/blog/know-your-apis-lessons-learned-from-resourcebundle",{"title":3617,"description":3626},"blog/know-your-apis-lessons-learned-from-resourcebundle",[677,2898,2900,3852],"resourcebundle","Last week I spent some time hunting down an internationalization-issue that came along while developing for a recent project. Let me explain what happened: Message-Lookup – of course – always…","kXv-V49VLTNQaYwmmCOhdlmLOL3IGkxpCAHfqpbUzrA",["Reactive",3856],{"$scookieConsent":3857,"$ssite-config":3859},{"functional":3858,"analytics":3858},false,{"_priority":3860,"env":3864,"name":3865,"url":3866},{"name":3861,"env":3862,"url":3863},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",3869],{"author-kannegiesser":-1,"roughlyFilteredArticles":-1},"/blog/author/kannegiesser"]