While useful for being able to extract those strings, that shouldn’t be necessary at all. MyAppWeb.ErrorHelper will run any ecto error through a translation step.
I tried to reproduce the issue you are facing and this is what I can say.
1- Adding bare custom message in the validate_length without gettext then editing by hand errors.pot then merging the new msgid with mix gettext.merge into errors.po won’t work.
2- Importing MyApp.gettext/1 in the changeset and passing the message through it, work as expected. In my case after running mix gettext.extract --merge and adding the translation in default.po work. I can see you’ve done something similar but with dgettext.
Now if you want to customize your message depending on the kind of error then just add multiple validations:
I realise you were saying that you are not getting the same error message as expected. That is weird, when I pass min and max in the same validation options I have the same error message as expected. I just tested it right now.
Yes dgettext can extract your messages in specific domain file such as errors.pot. But if you just use gettext then they will be found in default.pot and default.po.